package org.dftproject.familytreeseeker.core;

import java.io.StringReader;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dftproject.genesis.core.data.flow.Collector;
import org.dftproject.genesis.core.data.flow.DataBroker;
import org.dftproject.genesis.core.data.flow.ICollectorListener;
import org.dftproject.genesis.core.http.HttpManager;
import org.dftproject.genesis.core.search.ISearchProvider;
import org.dftproject.genesis.data.genealogy.GenealogyUtils;
import org.dftproject.genesis.data.genealogy.IName;
import org.dftproject.genesis.data.genealogy.INamePart;
import org.dftproject.genesis.data.genealogy.IPerson;
import org.dftproject.genesis.data.genealogy.NameConstants;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.Platform;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

public class SearchProvider implements ISearchProvider {

	private static final Log log = LogFactory.getLog(SearchProvider.class);

	public Set<IPerson> getPotentialDuplicates(final IPerson person) {
		final Set<IPerson> duplicates = new HashSet<IPerson>();
		
		IName name = GenealogyUtils.getName(person);
		if (name == null)
			return duplicates;

		// Create a thread pool for dereferencing links

		ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 2, 30, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());

		for (int i = 1; i <= 1; i++) {
			INamePart givenNamePart = null;
			for (INamePart part : name.getNameParts()) {
				if (!NameConstants.SURNAME.equals(part.getType())) {
					givenNamePart = part;
					break;
				}
			}
			
			String givenName = givenNamePart == null ? "" : "fn=" + givenNamePart.getValue() + "&";
				
			String content = HttpManager.getDefault().getWebPage("http://www.familytreeseeker.com/xmlsearch.php?" + givenName + "sn=" + GenealogyUtils.stringFromNameParts(GenealogyUtils.getNameParts(name, NameConstants.SURNAME)) + "&m=3&t=" + i);
			if (content != null) {
				// Extract search results

				Document dom = domFromString(content);

				NodeList links = XmlUtils.getNodeList(dom.getDocumentElement(), "person/link/text()");

				int n = links.getLength();
				for (int j = 0; j < n; j++) {
					// HACK: search results are ill-formed
					
					final String url = links.item(j).getNodeValue().replaceAll("&l=", "&i=");

					threadPool.execute(new Runnable() {

						public void run() {
							// Follow redirection

							String realUrl = HttpManager.getDefault().getRedirect(url);
							if (realUrl != null) {
								Collector<Object> adaptableCollector = new Collector<Object>();
								adaptableCollector.addListener(new ICollectorListener<Object>() {
	
									public void elementAdded(Collector<Object> collector, Object element) {
										IPerson duplicate = asPerson(element);
										if (duplicate != null)
											duplicates.add(duplicate);
									}
	
									public void providerStatusChanged(Collector<Object> collector, String providerName, boolean active) {
									}
									
								});
								DataBroker.getDefault().getRelatedObjects(realUrl, adaptableCollector);
							}
						}

					});
				}
			}
		}

		// Shutdown the thread pool and wait for it to terminate

		threadPool.shutdown();
		while (!threadPool.isTerminated()) {
			try {
				if (threadPool.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS))
					break;
			} catch (InterruptedException e) {
			}
		}
		
		return new HashSet<IPerson>(duplicates);
	}

	protected static Document domFromString(String source) {
		try {
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			factory.setAttribute("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
			factory.setValidating(false);
			DocumentBuilder builder = factory.newDocumentBuilder();
			return builder.parse(new InputSource(new StringReader(source)));
		} catch (Exception e) {
			log.error("Failed to parse XML", e);
			return null;
		}
	}

	protected IPerson asPerson(Object element) {
		if (IPerson.class.isInstance(element))
			return (IPerson) element;

		IPerson person = null;
		if (element instanceof IAdaptable)
			person = (IPerson) ((IAdaptable) element).getAdapter(IPerson.class);
		
		if (person == null)
			person = (IPerson) Platform.getAdapterManager().loadAdapter(element, IPerson.class.getName());

		return person;
	}
	
}
